defmodule EliPhxPlay.ChatSchema.Users do
  use Ecto.Schema
  import Ecto.Changeset

  # 因为迁移文件中用了 primary_key: false 和 :binary_id
  # 所以需要手动指定主键设置

  # @primary_key 是模块属性，告诉 Ecto 主键的信息
  @primary_key {:user_id, :binary_id, autogenerate: true}
  # 解释：
  # - :user_id 是主键字段名（对应迁移文件中的 add :user_id）
  # - :binary_id 是UUID类型（对应迁移文件中的 :binary_id）
  # - autogenerate: true 表示自动生成UUID

  # @foreign_key_type 定义外键类型，保持一致性
  @foreign_key_type :binary_id
  # 解释：如果这个表被其他表引用，外键也用 :binary_id 类型

  schema "users" do
    # schema "users" 对应数据库中的 users 表

    # 定义字段，格式：field :字段名, :类型, [选项]

    # 用户账号字段
    field :account, :string
    # 解释：
    # - :account 对应迁移文件中的 add :account, :string
    # - :string 表示字符串类型
    # - 这里不写 null: false，因为验证会在 changeset 中处理

    # 用户密码字段
    field :password, :string
    # 解释：对应迁移文件中的 add :password, :string

    # 用户昵称字段
    field :nickname, :string
    # 解释：对应迁移文件中的 add :nickname, :string

    # 用户头像链接字段
    field :avatar_url, :string
    # 解释：对应迁移文件中的 add :avatar_url, :string
    # 迁移文件中没有 null: false，所以这个字段可以为空

    # 用户状态字段
    field :status, :string, default: "active"
    # 解释：
    # - 对应迁移文件中的 add :status, :string, default: "active"
    # - default: "active" 表示默认值是 "active"

    # 状态到期时间字段
    field :status_until, :naive_datetime
    # 解释：
    # - 对应迁移文件中的 add :status_until, :naive_datetime
    # - :naive_datetime 是没有时区信息的日期时间类型

    # 添加反向关联
    # 每个用户可以拥有多个群组
    has_many :owned_groups, EliPhxPlay.ChatSchema.Groups,
      foreign_key: :owner_id,
      references: :user_id

    has_many :group_members, EliPhxPlay.ChatSchema.GroupMembers,
      foreign_key: :user_id,
      references: :user_id

    # 反向关联
    # 每个用户与其他用户关系
    has_many :user_relationships_a, EliPhxPlay.ChatSchema.UserRelationships,
      foreign_key: :user_a_id,
      references: :user_id

    has_many :user_relationships_b, EliPhxPlay.ChatSchema.UserRelationships,
      foreign_key: :user_b_id,
      references: :user_id

    has_many :sent_messages, EliPhxPlay.ChatSchema.Messages,
      foreign_key: :sender_id,
      references: :user_id


    # 时间戳字段（创建时间和更新时间）
    timestamps()
    # 解释：
    # - 对应迁移文件中的 timestamps()
    # - 会自动添加 inserted_at 和 updated_at 字段
    # - 类型是 :naive_datetime
  end

  def changeset(user, attrs) do

    # changeset 函数用于创建或更新数据
    user
    |> cast(attrs, [:account, :password, :nickname, :avatar_url, :status, :status_until])
    |> validate_required([:account, :password, :nickname],
      message: "不能为空")
    |> validate_length(:account, min: 3, max: 20,
      message: "账号长度应在3-20个字符之间")
    |> validate_format(:account, ~r/^[a-zA-Z0-9_]+$/,
      message: "账号只能包含字母、数字和下划线")
    |> unique_constraint(:account, name: :users_account_index,
      message: "账号已存在")
    |> validate_length(:password, min: 6, max: 100,
      message: "密码长度应在6-100个字符之间")
    |> encrypt_password()
  end

  # 🔑 加密密码的私有函数（模式匹配版本1）
  # 这个函数只在以下条件都满足时才会执行：
  # 1. changeset 是有效的（valid?: true，即所有验证都通过）
  # 2. changeset 的变更中包含 password 字段
  # 3. 能够提取出 password 的值
  defp encrypt_password(%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset) do
    # 使用 Bcrypt 对明文密码进行加密（单向散列）
    # hash_pwd_salt 会生成类似 "$2b$12$randomsalt.hashedvalue" 的字符串
    encrypted_password = Bcrypt.hash_pwd_salt(password)

    # 将 changeset 中的 password 字段替换为加密后的密码
    # 这样存储到数据库的就是加密密码，而不是明文
    put_change(changeset, :password, encrypted_password)
  end

  # 🔑 加密密码的私有函数（模式匹配版本2 - 兜底处理）
  # 当上面的条件不满足时执行这个版本：
  # - changeset 验证失败（valid?: false）
  # - 或者没有 password 字段需要处理
  # - 或者 password 字段为空
  # 这种情况下直接返回原 changeset，不做任何加密处理
  defp encrypt_password(changeset), do: changeset

  # 🔑 验证密码的公共函数
  # 用于登录时验证用户输入的明文密码是否与数据库中的加密密码匹配
  # 参数1：用户结构体，从中提取 password 字段（这是加密后的密码）
  # 参数2：用户输入的明文密码
  def valid_password?(%__MODULE__{password: hashed_password}, plain_password) do
    # 使用 Bcrypt.verify_pass 进行密码验证
    # 这个函数的工作原理：
    # 1. 从 hashed_password 中提取盐值（salt）
    # 2. 使用相同的盐值对 plain_password 进行散列
    # 3. 比较散列结果是否与 hashed_password 相同
    # 4. 返回 true（密码正确）或 false（密码错误）
    #
    # 注意：这里不是解密，而是重新加密后比对
    # 因为散列是单向的，不能反向解密
    Bcrypt.verify_pass(plain_password, hashed_password)
  end



end
# 解释：
